
package serv

import (
	"github.com/ant0ine/go-json-rest/rest"
	"rocks/util"
	"strconv"
	"net/http"
	"log"
	"io/ioutil"
	"bytes"
	"errors"
	"github.com/missionMeteora/binny.v2"
)

type RSInterface interface {
	// Needed for the NewRoutes to be able to register handlers
	Get(w rest.ResponseWriter, r *rest.Request)
	Post(w rest.ResponseWriter, r *rest.Request)
	GetWithOnlyTtl(w rest.ResponseWriter, r *rest.Request)
	PostWithOnlyTtl(w rest.ResponseWriter, r *rest.Request)
	GetWithOnlyNeh(w rest.ResponseWriter, r *rest.Request)
	PostWithOnlyNeh(w rest.ResponseWriter, r *rest.Request)
	GetWithTtlAndNeh(w rest.ResponseWriter, r *rest.Request)
	PostWithTtlAndNeh(w rest.ResponseWriter, r *rest.Request)
}
type RSService struct {
	RSInterface
}

type RobjConf struct {
	Method  string
	Ttl     int
	EOutput bool
	Body []byte //cacheKey backendRequest will use request.body.
}

func interfaceToString(i interface{})(str string ,err error){
	if i==nil{
		str=""
		return
	}
	switch v := i.(type) {
	case string:
		str=v
	case int:
		str=strconv.Itoa(i.(int))
	case int64:
		str=strconv.Itoa(int(i.(int64)))
	case float64:
		str=strconv.FormatFloat(i.(float64),'f',-1,64)
	default:
		err=errors.New("interfaceToString:unexpect field type ")
	}
	return
}

func getCKeyFromRobj( robj *RobjConf,r *rest.Request) (ckey string,err error){
	params:=r.URL.Query()
	//pkv:= map[string]interface{}{}
	//if (robj.Method=="POST" && len(robj.Body)>0){
	//	err=json.Unmarshal(robj.Body,&pkv)
	//	if err==nil{
	//		for k,v:= range pkv{
	//			strv,err1:=interfaceToString(v)
	//			err=err1
	//			if err!=nil{break}
	//			params.Set(k,strv)
	//		}
	//	} else{
	//		util.LogRed("getCKeyFrom Post Body:",string(robj.Body))
	//	}
	//}
	if (robj.Method=="POST" && len(robj.Body)>0){
		hashbody:=util.GetMd5String(string(robj.Body))
		params.Set("hashbody",hashbody)
	}
	if r.Header.Get("token")!=""{
		params.Set("token",r.Header.Get("token"))
	}
	if r.Header.Get("ukey")!=""{
		params.Set("ukey",r.Header.Get("ukey"))
	}

	ckey=r.PathParam("urlWithoutSchecma")
	if(len(params)>0){
		ckey+="?"+params.Encode()
	}
	return
}

func getBackReq( robj *RobjConf,r *rest.Request) (*http.Request ,error){
	backUrl:="http://"+r.PathParam("urlWithoutSchecma")
	if(len(r.URL.RawQuery)>0){
		backUrl+="?"+r.URL.RawQuery
	}
	util.LogGray("fetch from backend with url:",backUrl,"body:",string(robj.Body))
	backreq,err:=http.NewRequest(r.Method,backUrl,bytes.NewReader(robj.Body))
	if len(r.Header.Get("token"))>0{
		backreq.Header.Set("token",r.Header.Get("token"))
	}
	if len(r.Header.Get("ukey"))>0{
		backreq.Header.Set("ukey",r.Header.Get("ukey"))
	}
	return backreq,err
}

func ProceReq(robj *RobjConf, w rest.ResponseWriter, r *rest.Request) {
	if robj.Method=="POST"{
		bs,_:=ioutil.ReadAll(r.Body)
		robj.Body=bs
	}
	util.Log("RobjConf:",robj.Method,"ttl:",robj.Ttl,";body:",string(robj.Body))

	key,err:=getCKeyFromRobj(robj,r)
	if err==nil{
		resHeader,err4:=getHeaderFromCache("H_"+key)
		err=err4
		if err==nil&& resHeader!=nil{
			if(resHeader.LM==r.Header.Get("If-Modified-Since")){
				outPutTTL([]byte("H_"+key),w)
				w.WriteHeader(http.StatusNotModified)
				return
			}
			outputResStructHeader(resHeader,w)
			w.Header().Set("hith","true")

		}

		bs,err3:=util.Ldb.Get([]byte(key))
		err=err3
		if err==nil && len(bs)>0{
			log.Println("cache hit",key)
			w.Header().Set("hitc","true")
			if(robj.EOutput){
				outPutTTL([]byte(key),w)
			}
			outPutAge([]byte(key),w)
			w.(http.ResponseWriter).Write(bs)
			return
		}else {
			log.Println("cache miss for key:", key," err:",err)

			backreq,err2:=getBackReq(robj,r)
			err=err2
			if err==nil{
				backres,err1:=getBackEndData(backreq)
				err=err1
				if err==nil{
					//only cache 200
					if backres.Status==200{
						//cache content
						err6:=util.Ldb.SetEX([]byte(key),int64(robj.Ttl*CacheDuartionUnit),backres.Bytes )
						if err6!=nil{
							util.LogRed("setContentToCache err:",key,err)
						}
						//cache header
						resHeader:=*backres
						resHeader.Bytes=[]byte{}
						bs1,err5:= binny.Marshal(resHeader)
						err=err5
						if err==nil{
							err=util.Ldb.SetEX([]byte("H_"+key),int64(robj.Ttl*CacheDuartionUnit),bs1 )
						}else{
							util.LogRed("setHeaderToCache err:",err)
						}
						//util.Ldb.SetEX([]byte(key),robj.Ttl,backres.Bytes )
					}else{
						util.LogRed("backend err with status:",backres.Status,key,err)
					}

					if(robj.EOutput){
						outPutTTL([]byte(key),w)
					}
					outPutAge([]byte(key),w)
					outputResStructHeader(backres,w)
					//log.Println(backres)
					w.(http.ResponseWriter).Write(backres.Bytes)


				}
			}

			//
			//request, err := http.NewRequest("GET", url , nil)
			//if err != nil {
			//return nil,err
			//}
		}

	}

	if err!=nil{
		rest.Error(w,err.Error(),http.StatusBadRequest)
	}

}
var CacheDuartionUnit int=120;

func getHeaderFromCache(key string)(*resStruct,error){
	bs,err:=util.Ldb.Get([]byte(key))
	if err==nil && len(bs)>0 {
		resHeader := new(resStruct)
		err = binny.Unmarshal(bs, resHeader)
		return  resHeader,err
	}
	return  nil,err
}



func (rs *RSService) Get(w rest.ResponseWriter, r *rest.Request) {
	robj := &RobjConf{
		Method:"GET",
		Ttl:1,
		EOutput:true,
	}
	ProceReq(robj, w, r)
}
func (rs *RSService) Post(w rest.ResponseWriter, r *rest.Request) {
	robj := &RobjConf{
		Method:"POST",
		Ttl:1,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}

func (rs *RSService) GetOnlyWithTtl(w rest.ResponseWriter, r *rest.Request) {
	ttl, err := strconv.Atoi(r.PathParam("ttl"))
	if err != nil {
		rest.Error(w, "ttl error", http.StatusBadRequest)
		return
	}
	robj := &RobjConf{
		Method:"GET",
		Ttl:ttl,
		EOutput:true,
	}
	ProceReq(robj, w, r)
}
func (rs *RSService) PostOnlyWithTtl(w rest.ResponseWriter, r *rest.Request) {
	ttl, err := strconv.Atoi(r.PathParam("ttl"))
	if err != nil {
		rest.Error(w, "ttl error", http.StatusBadRequest)
		return
	}
	robj := &RobjConf{
		Method:"POST",
		Ttl:ttl,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}

func (rs *RSService) GetWithTtlAndNeh(w rest.ResponseWriter, r *rest.Request) {
	ttl, err := strconv.Atoi(r.PathParam("ttl"))
	if err != nil {
		rest.Error(w, "ttl error", http.StatusBadRequest)
		return
	}
	robj := &RobjConf{
		Method:"GET",
		Ttl:ttl,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}
func (rs *RSService) PostWithTtlAndNeh(w rest.ResponseWriter, r *rest.Request) {
	ttl, err := strconv.Atoi(r.PathParam("ttl"))
	if err != nil {
		rest.Error(w, "ttl error", http.StatusBadRequest)
		return
	}
	robj := &RobjConf{
		Method:"POST",
		Ttl:ttl,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}
func (rs *RSService) GetOnlyWithNeh(w rest.ResponseWriter, r *rest.Request) {

	robj := &RobjConf{
		Method:"GET",
		Ttl:1,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}
func (rs *RSService) PostOnlyWithNeh(w rest.ResponseWriter, r *rest.Request) {
	robj := &RobjConf{
		Method:"POST",
		Ttl:1,
		EOutput:false,
	}
	ProceReq(robj, w, r)
}
